#![cfg_attr(not(feature = "std"), no_std)]

use frame_support::{
    decl_module, decl_storage, decl_event, decl_error, ensure, StorageMap,
};
use frame_system::ensure_signed;
use sp_std::vec::Vec;
use sp_std::str::FromStr;
use sp_std::str;

use codec::{Encode, Decode};
use frame_support::sp_runtime::RuntimeDebug;

// use codec_derive::{Encode, Decode};

// use sp_runtime::traits::Lookup;
// use trie_db::Lookup;

#[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug)]
pub struct ProofObject {
    data: Vec<u8>,
}


// 2. Configuration
/// 通过指定它所依赖的参数和类型来配置模块。
pub trait Config: frame_system::Config {
    /// Because this pallet emits events, it depends on the runtime's definition of an event.
    type Event: From<Event<Self>> + Into<<Self as frame_system::Config>::Event>;
}

// 3. 存储
//  模块的 runtime 存储项目。
// https://substrate.dev/docs/en/knowledgebase/runtime/storage
decl_storage! {
    trait Store for Module<T: Config> as TemplateModule {
        // 定义存储结构，键值是 一个 Vec<u8> 存储一个元组 (T::AccountId, T::BlockNumber)
        Proofs: map hasher(blake2_128_concat) Vec<u8> => (T::AccountId, T::BlockNumber);
        // ProofsOfUser: map hasher(twox_64_concat) T::AccountId => Vec<ProofObject>;
        ProofsOfUser: map hasher(twox_64_concat) T::AccountId => Vec<u8>;
    }
}

// 4. 事件
// 当做出重要更改时，模块通过事件通知用户。
// 事件文档应该以提供参数描述名称的数组结尾。
// https://substrate.dev/docs/en/knowledgebase/runtime/events
decl_event! {
    pub enum Event<T> where AccountId = <T as frame_system::Config>::AccountId, BlockNumber = <T as frame_system::Config>::BlockNumber {
        /// Event emitted when a proof has been claimed. [who, claim]
        ClaimCreated(AccountId, Vec<u8>),
        /// Event emitted when a claim is revoked by the owner. [who, claim]
        ClaimRevoked(AccountId, Vec<u8>),
        // 定义一个转移存证的事件通知
        ClaimTransfer(AccountId, AccountId, BlockNumber),
    }
}

// 5. 错误
// 错误通知：出错时对用户进行通知
decl_error! {
    pub enum Error for Module<T: Config> {
        /// The proof has already been claimed.
        ProofAlreadyClaimed,
        /// 该证明不存在，因此它不能被撤销
        NoSuchProof,
        /// 该证明已经被另一个账号声明，因此它不能被撤销
        NotProofOwner,
    }
}

// 6. 可调用函数
// 可调用函数允许用户与模块交互并调用状态更改。
// 这些函数将实现为“Extrinsics”，它经常被拿来和交易做对比。
// 可调用函数必须标注 Weight 并返回调用结果。
decl_module! {
    pub struct Module<T: Config> for enum Call where 
        origin: T::Origin 
    {
        // Errors must be initialized if they are used by the pallet.
        type Error = Error<T>;

        // 事件必须被初始化，如果它们被模块所使用。
        fn deposit_event() = default;

        /// 转移存证 , 将某个存证的信息转移到另外一个 AccountId 下
        #[weight = 0]
        fn transfer_claim(origin , receiver_address: T::AccountId, proof: Vec<u8> ) {
            let sender = ensure_signed(origin)?;

            // 1、读取存证信息，主要就是存证的值
            let (owner, value) = Proofs::<T>::get(&proof);

            // 2、验证当前的调用者是证声明的所有者，如果不匹配返回 NotProofOwner
            ensure!(sender == owner, Error::<T>::NotProofOwner);

            // 3、删除存证。 // 直接调用 revoke_claim(origin, proof); 不会调用
            // revoke_claim(origin, proof);  // 不会调用
            // 校验存证签名是否存在
            ensure!(Proofs::<T>::contains_key(&proof), Error::<T>::NoSuchProof);
            ensure!(ProofsOfUser::<T>::contains_key(&sender), Error::<T>::NoSuchProof);

            // 从存储中移除声明
            Proofs::<T>::remove(&proof);
            ProofsOfUser::<T>::remove(&owner);

            // 4、将存证转移到新地址中
            Proofs::<T>::insert(&proof, (&receiver_address, value));
            ProofsOfUser::<T>::insert(&receiver_address, &proof);

            // 5、发送事件通知：
            Self::deposit_event(RawEvent::ClaimTransfer(sender, receiver_address, value));
        }

        /// 允许用户队未声明的证明拥有所有权
        #[weight = 0]
        fn create_claim(origin, proof: Vec<u8>)  {
            // 检查 extrinsic 是否签名并获得签名者
            // 如果 extrinsic 未签名，此函数将返回一个错误。
            // https://substrate.dev/docs/en/knowledgebase/runtime/origin
            let sender = ensure_signed(origin)?;

            // 校验指定的证明是否被声明
            ensure!(!Proofs::<T>::contains_key(&proof), Error::<T>::ProofAlreadyClaimed);

            // 从 FRAME 系统模块中获取区块号.
            let current_block = <frame_system::Module<T>>::block_number();

            // 存储证明：发送人与区块号，这个在 Proofs 中得到定义
            Proofs::<T>::insert(&proof, (&sender, current_block));

            // // 存储权证列表值
            // let proof_list = ProofObject {
            //     data: proof.clone(),
            // } ;

            // let mut proof_vec = Vec::<ProofObject>::new();
            // proof_vec.push(proof_list);

            // 插入一个以 AccountId 索引的，存证信息，用于按照用户 AccountId 查询对应的存证信息
            ProofsOfUser::<T>::insert(&sender, &proof);

            // 声明创建后，发送事件
            Self::deposit_event(RawEvent::ClaimCreated(sender, proof));

        }
        /// 允许证明所有者撤回声明
        #[weight = 0]
        fn revoke_claim(origin, proof: Vec<u8>) {
      
            // https://substrate.dev/docs/en/knowledgebase/runtime/origin
            let sender = ensure_signed(origin)?;
            // 检查存证信息是否存在，首先是存在对应的区块高度信息
            ensure!(Proofs::<T>::contains_key(&proof), Error::<T>::NoSuchProof);
            // 检查用户索引的存储信息是否存在，理论上和上面的 Proofs 是一对儿信息
            ensure!(ProofsOfUser::<T>::contains_key(&sender), Error::<T>::NoSuchProof);

            // 获取声明的所有者
            let (owner, _) = Proofs::<T>::get(&proof);
            // 验证当前的调用者是证声明的所有者，如果不匹配返回 NotProofOwner
            ensure!(sender == owner, Error::<T>::NotProofOwner);
            // 从存储中移除声明
            Proofs::<T>::remove(&proof);
            ProofsOfUser::<T>::remove(&sender);
            // 声明抹掉后，发送事件
            Self::deposit_event(RawEvent::ClaimRevoked(sender, proof));

        }

        
    }
} 